﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Timers;

namespace Dasaar.Interaction.K8061
{
    public class K8061Manager
    {
        #region Eventing
        public delegate void DigitalValueChangedHandler(int Unit, int Channel, bool NewValue, bool OldValue);
        public delegate void AnalogValueChangedHandler(int Unit, int Channel, int NewValue, int OldValue);
        public event DigitalValueChangedHandler DigitalValueChanged;
        public event AnalogValueChangedHandler AnalogValueChanged;
        protected void OnDigitalValueChanged(int Unit, int Channel, bool NewValue, bool OldValue)
        {
            if (DigitalValueChanged != null)
            {
                DigitalValueChanged(Unit, Channel, NewValue, OldValue);
            }
        }
        protected void OnAnalogValueChanged(int Unit, int Channel, int NewValue, int OldValue)
        {
            if (AnalogValueChanged != null)
            {
                AnalogValueChanged(Unit, Channel, NewValue, OldValue);
            }
        }
        #endregion

        #region K8061.dll Imports
        [DllImport("k8061.dll")]
        private static extern int OpenDevice();
        [DllImport("k8061.dll")]
        private static extern void CloseDevices();
        [DllImport("k8061.dll")]
        private static extern int ReadAnalogChannel(int CardAddress, int Channell);
        [DllImport("k8061.dll")]
        private static extern void ReadAllAnalog(int CardAddress, ref int[] data);
        [DllImport("k8061.dll")]
        private static extern void OutputAnalogChannel(int CardAddress, int Channel, int Data);
        [DllImport("k8061.dll")]
        private static extern void OutputAllAnalog(int CardAddress, ref int[] data);
        [DllImport("k8061.dll")]
        private static extern void ClearAnalogChannel(int CardAddress, int Channel);
        [DllImport("k8061.dll")]
        private static extern void ClearAllAnalog(int CardAddress);
        [DllImport("k8061.dll")]
        private static extern void SetAnalogChannel(int CardAddres, int Channel);
        [DllImport("k8061.dll")]
        private static extern void SetAllAnalog(int CardAddress);
        [DllImport("k8061.dll")]
        private static extern void OutputAllDigital(int CardAddress, int data);
        [DllImport("k8061.dll")]
        private static extern void ClearDigitalChannel(int CardAddress, int Channel);
        [DllImport("k8061.dll")]
        private static extern void ClearAllDigital(int CardAddress);
        [DllImport("k8061.dll")]
        private static extern void SetDigitalChannel(int CardAddress, int Channel);
        [DllImport("k8061.dll")]
        private static extern void SetAllDigital(int CardAddress);
        [DllImport("k8061.dll")]
        private static extern bool ReadDigitalChannel(int CardAddress, int Channel);
        [DllImport("k8061.dll")]
        private static extern int ReadAllDigital(int CardAddress);
        [DllImport("k8061.dll")]
        private static extern void OutputPWM(int CardAddress, int Data);
        [DllImport("k8061.dll")]
        private static extern bool PowerGood(int CardAddress);
        [DllImport("k8061.dll")]
        private static extern bool Connected(int CardAddress);
        [DllImport("k8061.dll")]
        private static extern void ReadVersion(int CardAddress, ref int[] Buffer);
        #endregion

        #region Properties
        private Dictionary<string, int> portStatus;
        private Timer timer;
        private List<int> unitList;

        public List<int> Units
        {
            get
            {
                return unitList;
            }
        }
        public int ScanInterval
        {
            get
            {
                return int.Parse(timer.Interval.ToString());
            }
            set
            {
                if (value != 0)
                {
                    timer.Interval = value * 1000;
                    timer.Enabled = true;
                }
                else
                {
                    timer.Interval = 0;
                    timer.Enabled = false;
                }
            }
        }
        #endregion

        #region Contructor and Destructor
        public K8061Manager(int ScanIntervall)
        {
            unitList = new List<int>();
            portStatus = new Dictionary<string, int>();
            InitUnits();
            InitTimer(ScanIntervall);
        }
        public K8061Manager()
        {
            unitList = new List<int>();
            portStatus = new Dictionary<string, int>();
            InitUnits();
            InitTimer(0);
        }
        ~K8061Manager()
        {
            CloseDevices();
        }
        #endregion

        #region Private functions
        private void FlushInputs()
        {
            portStatus.Clear();
            foreach (int unit in unitList)
            {
                for (int i = 0; i < 7; i++)
                {
                    portStatus.Add(string.Format("d,in,{0},{1}", unit, i), BoolToInt(ReadDigitalChannel(unit, i)));
                    portStatus.Add(string.Format("a,in,{0},{1}", unit, i), ReadAnalogChannel(unit, i));
                }
            }
        }
        private void FlushOutputs()
        {
            portStatus.Clear();
            foreach (int unit in unitList)
            {
                for (int i = 0; i < 7; i++)
                {
                    SetDigitalOutput(unit, i, false);
                    portStatus.Add(string.Format("d,out,{0},{1}", unit, i), 0);

                    SetAnalogOutput(unit, i, 0);
                    portStatus.Add(string.Format("a,out,{0},{1}", unit, i), 0);
                }

                SetPWMOutput(unit, 0);
                portStatus.Add(string.Format("p,out,{0},0", unit), 0);
            }
        }
        private void InitTimer(int intervall)
        {
            timer = new Timer();
            timer.Interval = intervall * 1000;
            timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
            if (intervall > 0)
                timer.Enabled = true;
            else
                timer.Enabled = false;
        }
        private void timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            int newValue = 0;
            int oldValue = 0;

            foreach (int unit in unitList)
            {
                for (int i = 0; i < 7; i++)
                {
                    newValue = BoolToInt(ReadDigitalChannel(unit, i));
                    oldValue = portStatus[string.Format("d,in,{0},{1}", unit, i)];
                    if (newValue != oldValue)
                    {
                        OnDigitalValueChanged(unit, i, IntToBool(newValue), IntToBool(oldValue));
                        portStatus[string.Format("d,in,{0},{1}", unit, i)] = newValue;
                    }

                    newValue = ReadAnalogChannel(unit, i);
                    oldValue = portStatus[string.Format("a,in,{0},{1}", unit, i)];
                    if (newValue != oldValue)
                    {
                        OnAnalogValueChanged(unit, i, newValue, oldValue);
                        portStatus[string.Format("a,in,{0},{1}", unit, i)] = newValue;
                    }
                }
            }
        }
        private void CheckUnitFunction(int Unit)
        {
            if (Connected(Unit))
            {
                if (!PowerGood(Unit))
                    throw new HardwareException(string.Format("IC6 not powered or data link between IC3 and IC6 not working at unit {0}.", Unit));
            }
            else
                throw new HardwareException(string.Format("Hardware connection lost with unit {0},", Unit));
        }
        private void InitUnits()
        {
            bool detecting = true;

            while (detecting)
            {
                int newDevice = OpenDevice();

                switch (newDevice)
                {
                    case 0:
                    case 1:
                    case 2:
                    case 3:
                    case 4:
                    case 5:
                    case 6:
                    case 7:
                        unitList.Add(newDevice);
                        break;
                    case -1:
                        detecting = false;
                        break;
                    case -2:
                        throw new HardwareException("No units found/connected.");
                    default:
                        throw new HardwareException(string.Format("Unexpected value recived when initializing hardware ({0}).", newDevice));
                }
            }
            FlushInputs();
            FlushOutputs();
        }
        private int BoolToInt(bool input)
        {
            if (input)
                return 1;
            else
                return 0;
        }
        private bool IntToBool(int input)
        {
            if (input < 1)
                return false;
            else
                return true;
        }
        #endregion

        #region Public supporting functions
        public void Restart()
        {
            int oldScanIntervall = ScanInterval;
            ScanInterval = 0;
            CloseDevices();
            InitUnits();
            ScanInterval = oldScanIntervall;
        }
        public void Restart(int ScanInterval)
        {
            int oldScanIntervall = ScanInterval;
            ScanInterval = 0;
            CloseDevices();
            InitUnits();
            ScanInterval = oldScanIntervall;
        }
        #endregion

        #region Inputs
        public bool GetDigitalInput(int Unit, int Channel)
        {
            CheckUnitFunction(Unit);
            if (unitList.Contains(Unit))
            {
                bool temp = ReadDigitalChannel(Unit, Channel);
                portStatus[string.Format("d,in,{0},{1}", Unit, Channel)] = BoolToInt(temp);
                return temp;
            }
            else
                throw new InvalidUnitException("The specified unit is not connected");
        }
        public int GetAnalogInput(int Unit, int Channel)
        {
            CheckUnitFunction(Unit);
            if (unitList.Contains(Unit))
            {
                int temp = ReadAnalogChannel(Unit, Channel);
                portStatus[string.Format("a,in,{0},{1}", Unit, Channel)] = temp;
                return temp;
            }
            else
                throw new InvalidUnitException("The specified unit is not connected");
        }
        #endregion

        #region Outputs
        public void SetDigitalOutput(int Unit, int Channel, bool Value)
        {
            CheckUnitFunction(Unit);
            if (unitList.Contains(Unit))
            {
                if (Value)
                {
                    SetDigitalChannel(Unit, Channel);
                    portStatus[string.Format("d,out,{0},{1}", Unit, Channel)] = 1;
                }
                else
                {
                    ClearDigitalChannel(Unit, Channel);
                    portStatus[string.Format("d,out,{0},{1}", Unit, Channel)] = 0;
                }
            }
            else
                throw new InvalidUnitException("The specified unit is not connected");
        }
        public void SetAnalogOutput(int Unit, int Channel, int Value)
        {
            if (unitList.Contains(Unit))
            {
                CheckUnitFunction(Unit);
                if (Value == 0)
                {
                    ClearAnalogChannel(Unit, Channel);
                    portStatus[string.Format("a,out,{0},{1}", Unit, Channel)] = 0;
                }
                else
                    if (Value < 255)
                    {
                        OutputAnalogChannel(Unit, Channel, Value);
                        portStatus[string.Format("d,out,{0},{1}", Unit, Channel)] = Value;
                    }
                    else
                        throw new ValueOutOfRangeException("Analog values must be between 0 and 255");
            }
            else
                throw new InvalidUnitException("The specified unit is not initialized.");
        }
        public bool GetDigitalOutput(int Unit, int Channel)
        {
            if (unitList.Contains(Unit))
            {
                CheckUnitFunction(Unit);
                if (portStatus[string.Format("d,out,{0},{1}", Unit, Channel)] == 0)
                    return false;
                else
                    return true;
            }
            else
                throw new InvalidUnitException("The specified unit is not initialized.");
        }
        public int GetAnalogOutput(int Unit, int Channel)
        {
            if (unitList.Contains(Unit))
            {
                CheckUnitFunction(Unit);
                return portStatus[string.Format("a,out,{0},{1}", Unit, Channel)];
            }
            else
                throw new InvalidUnitException("The specified unit is not initialized.");
        }
        #endregion

        #region PWM
        public void SetPWMOutput(int Unit, int Value)
        {
            if (unitList.Contains(Unit))
            {
                CheckUnitFunction(Unit);
                if (Value < 1024 && Value > -1)
                {
                    OutputPWM(Unit, Value);
                    portStatus[string.Format("d,out,{0},0", Unit)] = Value;
                }
                else
                    throw new ValueOutOfRangeException("PWM values must be between 0 and 1023");
            }
            else
                throw new InvalidUnitException("The specified unit is not initialized.");

        }
        public int GetPWMOutput(int Unit)
        {
            if (unitList.Contains(Unit))
            {
                CheckUnitFunction(Unit);
                return portStatus[string.Format("p,out,{0},0", Unit)];
            }
            else
                throw new InvalidUnitException("The specified unit is not initialized.");
        }
        #endregion

    }

}
